Box streaming

In this notebook, we will explore other capabilities of the hvPlot ecosystem.

In particular, we will look at the hist and BoxDraw stream methods, using the AIMS eReefs database.

See also

For other inspirational functionalities, you might be interested in the examples from the HoloViews and GeoViews gallery.

Load the required Python libraries

First of all, load the necessary libraries. These are the ones we discussed previously:

  • numpy

  • matplotlib

  • cartopy

  • pandas

  • xarray

  • holoviews

  • geoviews

import os
import numpy as np
import pandas as pd
import xarray as xr

import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
cartopy.config['data_dir'] = os.getenv('CARTOPY_DIR', cartopy.config.get('data_dir'))

import cmocean
import hvplot.xarray

import holoviews as hv
from holoviews import opts, dim

import geoviews as gv
import geoviews.feature as gf
from geoviews import tile_sources as gvts

from cartopy import crs

gv.extension('bokeh')

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning) 

Build a multi-file dataset

We will use the open_mfdataset function from xArray to open multiple netCDF files into a single xarray Dataset.

We will query load the GBR4km dataset from the AIMS server, so let’s first define the base URL:

base_url = "http://thredds.ereefs.aims.gov.au/thredds/dodsC/s3://aims-ereefs-public-prod/derived/ncaggregate/ereefs/gbr4_v2/daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-"

For the sake of the demonstration, we will only use 2 months:

month_st = 3   # Starting month (March)
month_ed = 4   # Ending month (April)
year = 2018    # Year

# Based on the server the file naming convention 
hydrofiles = [f"{base_url}{year}-{month:02}.nc" for month in range(month_st, month_ed+1)]
hydrofiles
['http://thredds.ereefs.aims.gov.au/thredds/dodsC/s3://aims-ereefs-public-prod/derived/ncaggregate/ereefs/gbr4_v2/daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-2018-03.nc',
 'http://thredds.ereefs.aims.gov.au/thredds/dodsC/s3://aims-ereefs-public-prod/derived/ncaggregate/ereefs/gbr4_v2/daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-2018-04.nc']

Loading the dataset into xArray

Using xArray, we open these files into a Dataset:

ds_hydro = xr.open_mfdataset(hydrofiles)
ds_hydro
<xarray.Dataset>
Dimensions:      (k: 17, latitude: 723, longitude: 491, time: 61)
Coordinates:
  * time         (time) datetime64[ns] 2018-02-28T14:00:00 ... 2018-04-29T14:...
    zc           (k) float64 dask.array<chunksize=(17,), meta=np.ndarray>
  * latitude     (latitude) float64 -28.7 -28.67 -28.64 ... -7.096 -7.066 -7.036
  * longitude    (longitude) float64 142.2 142.2 142.2 ... 156.8 156.8 156.9
Dimensions without coordinates: k
Data variables:
    mean_cur     (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 723, 491), meta=np.ndarray>
    salt         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 723, 491), meta=np.ndarray>
    temp         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 723, 491), meta=np.ndarray>
    u            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 723, 491), meta=np.ndarray>
    v            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 723, 491), meta=np.ndarray>
    mean_wspeed  (time, latitude, longitude) float32 dask.array<chunksize=(31, 723, 491), meta=np.ndarray>
    eta          (time, latitude, longitude) float32 dask.array<chunksize=(31, 723, 491), meta=np.ndarray>
    wspeed_u     (time, latitude, longitude) float32 dask.array<chunksize=(31, 723, 491), meta=np.ndarray>
    wspeed_v     (time, latitude, longitude) float32 dask.array<chunksize=(31, 723, 491), meta=np.ndarray>
Attributes: (12/21)
    Conventions:                     CF-1.0
    NCO:                             4.4.4
    Run_ID:                          2
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    aims_ncaggregate_buildDate:      2020-08-21T14:27:56+10:00
    aims_ncaggregate_datasetId:      products__ncaggregate__ereefs__gbr4_v2__...
    ...                              ...
    paramhead:                       GBR 4km resolution grid
    shoc_version:                    v1.1 rev(5620)
    technical_guide_link:            https://eatlas.org.au/pydio/public/aims-...
    technical_guide_publish_date:    2020-08-18
    title:                           eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d...
    DODS_EXTRA.Unlimited_Dimension:  time

Clip the Dataset

To reduce the Dataset size we will clip the spatial extent based on longitudinal and latitudinal values.

As we already saw this is easely done using the sel function with the slice method.

min_lon = 146.5   # lower left longitude
min_lat = -20     # lower left latitude
max_lon = 148     # upper right longitude
max_lat = -17     # upper right latitude

# Defining the boundaries
lon_bnds = [min_lon, max_lon]
lat_bnds = [min_lat, max_lat]

# Performing the reduction 
ds_hydro_clip = ds_hydro.sel(latitude=slice(*lat_bnds), longitude=slice(*lon_bnds))
ds_hydro_clip
<xarray.Dataset>
Dimensions:      (k: 17, latitude: 100, longitude: 50, time: 61)
Coordinates:
  * time         (time) datetime64[ns] 2018-02-28T14:00:00 ... 2018-04-29T14:...
    zc           (k) float64 dask.array<chunksize=(17,), meta=np.ndarray>
  * latitude     (latitude) float64 -20.0 -19.97 -19.94 ... -17.09 -17.06 -17.03
  * longitude    (longitude) float64 146.5 146.5 146.6 ... 147.9 148.0 148.0
Dimensions without coordinates: k
Data variables:
    mean_cur     (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    salt         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    temp         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    u            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    v            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    mean_wspeed  (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    eta          (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    wspeed_u     (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    wspeed_v     (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
Attributes: (12/21)
    Conventions:                     CF-1.0
    NCO:                             4.4.4
    Run_ID:                          2
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    aims_ncaggregate_buildDate:      2020-08-21T14:27:56+10:00
    aims_ncaggregate_datasetId:      products__ncaggregate__ereefs__gbr4_v2__...
    ...                              ...
    paramhead:                       GBR 4km resolution grid
    shoc_version:                    v1.1 rev(5620)
    technical_guide_link:            https://eatlas.org.au/pydio/public/aims-...
    technical_guide_publish_date:    2020-08-18
    title:                           eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d...
    DODS_EXTRA.Unlimited_Dimension:  time

Change coordinate name

To make it easier to process the xArray dataset, we change the zc coordinate to the same name as its dimension (i.e. k).

This is done like this:

# Creation of a new coordinate k with the same array as zc
ds_hydro_clip.coords['k'] = ('zc',ds_hydro_clip.zc)

# Swapping `zc` with `k`
ds_hydro_clip = ds_hydro_clip.swap_dims({'zc':'k'})

# Ok we can now safely remove `zc`
ds_hydro_clip = ds_hydro_clip.drop(['zc'])
ds_hydro_clip
<xarray.Dataset>
Dimensions:      (k: 17, latitude: 100, longitude: 50, time: 61)
Coordinates:
  * time         (time) datetime64[ns] 2018-02-28T14:00:00 ... 2018-04-29T14:...
  * latitude     (latitude) float64 -20.0 -19.97 -19.94 ... -17.09 -17.06 -17.03
  * longitude    (longitude) float64 146.5 146.5 146.6 ... 147.9 148.0 148.0
  * k            (k) float64 -145.0 -120.0 -103.0 -88.0 ... -5.55 -3.0 -1.5 -0.5
Data variables:
    mean_cur     (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    salt         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    temp         (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    u            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    v            (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 17, 100, 50), meta=np.ndarray>
    mean_wspeed  (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    eta          (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    wspeed_u     (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
    wspeed_v     (time, latitude, longitude) float32 dask.array<chunksize=(31, 100, 50), meta=np.ndarray>
Attributes: (12/21)
    Conventions:                     CF-1.0
    NCO:                             4.4.4
    Run_ID:                          2
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    aims_ncaggregate_buildDate:      2020-08-21T14:27:56+10:00
    aims_ncaggregate_datasetId:      products__ncaggregate__ereefs__gbr4_v2__...
    ...                              ...
    paramhead:                       GBR 4km resolution grid
    shoc_version:                    v1.1 rev(5620)
    technical_guide_link:            https://eatlas.org.au/pydio/public/aims-...
    technical_guide_publish_date:    2020-08-18
    title:                           eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d...
    DODS_EXTRA.Unlimited_Dimension:  time

Here we want to evaluate the surface salinity evolution over the temporal interval.

To proceed, we will only select the top 10 m of the dataset based on the z-coordinates definition.

# Take the top 10 m based on the z-coordinate (k dimension)
slice_ds = ds_hydro_clip.sel(k=slice(-10, 0)).drop(['u','v','eta',
                                                    'mean_wspeed',
                                                    'wspeed_u',
                                                    'wspeed_v'])
slice_ds
<xarray.Dataset>
Dimensions:    (k: 5, latitude: 100, longitude: 50, time: 61)
Coordinates:
  * time       (time) datetime64[ns] 2018-02-28T14:00:00 ... 2018-04-29T14:00:00
  * latitude   (latitude) float64 -20.0 -19.97 -19.94 ... -17.09 -17.06 -17.03
  * longitude  (longitude) float64 146.5 146.5 146.6 146.6 ... 147.9 148.0 148.0
  * k          (k) float64 -8.8 -5.55 -3.0 -1.5 -0.5
Data variables:
    mean_cur   (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
    salt       (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
    temp       (time, k, latitude, longitude) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
Attributes: (12/21)
    Conventions:                     CF-1.0
    NCO:                             4.4.4
    Run_ID:                          2
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    aims_ncaggregate_buildDate:      2020-08-21T14:27:56+10:00
    aims_ncaggregate_datasetId:      products__ncaggregate__ereefs__gbr4_v2__...
    ...                              ...
    paramhead:                       GBR 4km resolution grid
    shoc_version:                    v1.1 rev(5620)
    technical_guide_link:            https://eatlas.org.au/pydio/public/aims-...
    technical_guide_publish_date:    2020-08-18
    title:                           eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d...
    DODS_EXTRA.Unlimited_Dimension:  time

Now we will average the value along the k coordinate using the mean function and then load the Xarray Dataset in memory:

vards = slice_ds.mean(dim='k').salt.load()

Visualise temporal changes

We first load data using both Cartopy and GeoViews to check what we have…

# Loading coastlines from Cartopy
coastline = gf.coastline(line_width=3, line_color='k').opts(projection=ccrs.PlateCarree(), scale='10m')

# Loading land mask from Cartopy
land = gf.land.options(scale='10m', fill_color='lightgray')

# Let's put the Xarray dataset into a GeoViews object
var_mask = gv.Dataset(vards, crs=crs.PlateCarree())

# Print salinity range:
minvar = vards.min().item()
maxvar = vards.max().item()
print('Salinity range: ',minvar,maxvar)

# Create stack of images grouped by time
im_mask = var_mask.to(gv.Image, ['longitude', 'latitude'], dynamic=True)
Salinity range:  6.842176914215088 35.55593490600586

GeoViews interactive plot…

# Positioning the slider at the bottom
hv.output(widget_location='bottom')

# Figure title name
titleName = slice_ds.salt.long_name+' '+slice_ds.salt.units

# Plotting the interactive view
im_mask.opts(active_tools=['wheel_zoom', 'pan'], cmap=cmocean.cm.curl,
             colorbar=True, width=450, height=400, clim=(34,36),
             title=titleName) * coastline * land
/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/110m/physical/ne_110m_land.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)

By sliding over time, we can quickly visualise the average surface salinity…

Histogram plot

The first thing we will do is customize the appearance of the elements used in the rest of the notebook, using opts.defaults to declare the options we want to use ahead of time (see the User Guide for details).

opts.defaults(
    opts.GridSpace(shared_xaxis=True, shared_yaxis=True),
    opts.Image(cmap=cmocean.cm.curl, clim=(34,36)),
    opts.Labels(text_color='white', text_font_size='8pt', text_align='left', text_baseline='bottom'),
    opts.Path(color='white'),
    opts.Spread(width=600),
    opts.VLine(color='black'),
    opts.Curve(width=400, framewise=True), 
    opts.Polygons(fill_alpha=0.2, line_color='white'),
    opts.Overlay(show_legend=False))

Using the .to interface as above, we can map the dimensions of our Dataset onto the dimensions of an Element.

To display an image, we will pick the Image element and specify the longitude and latitude as the two key dimensions of each Image. Since our dataset has only a single value dimension (salt), we don’t need to declare it explicitly:

im_hist = var_mask.to(gv.Image, 
                      ['longitude', 'latitude']).hist().opts(width=500, height=450, 
                                                             title=titleName)
im_hist
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-13-537a24f9a1f6> in <module>
      2                       ['longitude', 'latitude']).hist().opts(width=500, height=450,
      3                                                              title=titleName)
----> 4 im_hist

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/IPython/core/displayhook.py in __call__(self, result)
    260             self.start_displayhook()
    261             self.write_output_prompt()
--> 262             format_dict, md_dict = self.compute_format_data(result)
    263             self.update_user_ns(result)
    264             self.fill_exec_result(result)

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/IPython/core/displayhook.py in compute_format_data(self, result)
    149 
    150         """
--> 151         return self.shell.display_formatter.format(result)
    152 
    153     # This can be set to True by the write_output_prompt method in a subclass

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/IPython/core/formatters.py in format(self, obj, include, exclude)
    148             return {}, {}
    149 
--> 150         format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
    151 
    152         if format_dict or md_dict:

<decorator-gen-5> in __call__(self, obj, include, exclude)

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/IPython/core/formatters.py in catch_format_error(method, self, *args, **kwargs)
    222     """show traceback on failed format call"""
    223     try:
--> 224         r = method(self, *args, **kwargs)
    225     except NotImplementedError:
    226         # don't warn on NotImplementedErrors

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/core/dimension.py in _repr_mimebundle_(self, include, exclude)
   1315         combined and returned.
   1316         """
-> 1317         return Store.render(self)
   1318 
   1319 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/core/options.py in render(cls, obj)
   1403         data, metadata = {}, {}
   1404         for hook in hooks:
-> 1405             ret = hook(obj)
   1406             if ret is None:
   1407                 continue

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py in pprint_display(obj)
    280     if not ip.display_formatter.formatters['text/plain'].pprint:
    281         return None
--> 282     return display(obj, raw_output=True)
    283 
    284 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py in display(obj, raw_output, **kwargs)
    253     elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    254         with option_state(obj):
--> 255             output = layout_display(obj)
    256     elif isinstance(obj, (HoloMap, DynamicMap)):
    257         with option_state(obj):

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py in wrapped(element)
    144         try:
    145             max_frames = OutputSettings.options['max_frames']
--> 146             mimebundle = fn(element, max_frames=max_frames)
    147             if mimebundle is None:
    148                 return {}, {}

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py in layout_display(layout, max_frames)
    218         return None
    219 
--> 220     return render(layout)
    221 
    222 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py in render(obj, **kwargs)
     66         renderer = renderer.instance(fig='png')
     67 
---> 68     return renderer.components(obj, **kwargs)
     69 
     70 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/holoviews/plotting/renderer.py in components(self, obj, fmt, comm, **kwargs)
    410                     model = plot.layout._render_model(doc, comm)
    411                 if embed:
--> 412                     return render_model(model, comm)
    413                 args = (model, doc, comm)
    414                 if panel_version > '0.9.3':

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/panel/io/notebook.py in render_model(model, comm)
    146     target = model.ref['id']
    147 
--> 148     (docs_json, [render_item]) = standalone_docs_json_and_render_items([model], True)
    149     div = div_for_render_item(render_item)
    150     render_item = render_item.to_json()

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/embed/util.py in standalone_docs_json_and_render_items(models, suppress_callback_warning)
    292     docs_json = {}
    293     for doc, (docid, _) in docs.items():
--> 294         docs_json[docid] = doc.to_json()
    295 
    296     render_items = []

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/document/document.py in to_json(self)
    900         # this is a total hack to go via a string, needed because
    901         # our BokehJSONEncoder goes straight to a string.
--> 902         doc_json = self.to_json_string()
    903         return loads(doc_json)
    904 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/document/document.py in to_json_string(self, indent)
    952             'roots' : {
    953                 'root_ids' : root_ids,
--> 954                 'references' : references_json(root_references)
    955             },
    956             'version' : __version__,

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/document/util.py in references_json(references)
    134     for r in references:
    135         struct = r.struct
--> 136         struct['attributes'] = r._to_json_like(include_defaults=False)
    137         references_json.append(struct)
    138 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/model.py in _to_json_like(self, include_defaults)
    732 
    733         '''
--> 734         all_attrs = self.properties_with_values(include_defaults=include_defaults)
    735 
    736         # If __subtype__ is defined, then this model may introduce properties

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/has_props.py in properties_with_values(self, include_defaults, include_undefined)
    587 
    588         '''
--> 589         return self.query_properties_with_values(lambda prop: prop.serialized,
    590             include_defaults=include_defaults, include_undefined=include_undefined)
    591 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/has_props.py in query_properties_with_values(self, query, include_defaults, include_undefined)
    646 
    647             try:
--> 648                 value = descriptor.serializable_value(self)
    649             except UnsetValueError:
    650                 if include_undefined:

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/descriptors.py in serializable_value(self, obj)
    293 
    294         """
--> 295         value = self.__get__(obj, obj.__class__)
    296         return self.property.serialize_value(value)
    297 

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/descriptors.py in __get__(self, obj, owner)
    497         """
    498         if obj is not None:
--> 499             value = self._get(obj)
    500 
    501             if value is not Undefined:

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/descriptors.py in _get(self, obj)
    698 
    699         if self.name not in obj._property_values:
--> 700             return self._get_default(obj)
    701         else:
    702             return obj._property_values[self.name]

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/descriptors.py in _get_default(self, obj)
    715         is_themed = obj.themed_values() is not None and self.name in obj.themed_values()
    716 
--> 717         default = self.instance_default(obj)
    718 
    719         if is_themed:

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/descriptors.py in instance_default(self, obj)
    592 
    593         """
--> 594         return self.property.themed_default(obj.__class__, self.name, obj.themed_values())
    595 
    596     def set_from_json(self, obj, json, models=None, setter=None):

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/property/bases.py in themed_default(self, cls, name, theme_overrides)
    186         overrides = theme_overrides
    187         if overrides is None or name not in overrides:
--> 188             overrides = cls._overridden_defaults()
    189 
    190         if name in overrides:

/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/bokeh/core/has_props.py in _overridden_defaults(cls)
    598 
    599         '''
--> 600         return accumulate_dict_from_superclasses(cls, "__overridden_defaults__")
    601 
    602     @classmethod

KeyboardInterrupt: 

As usual, the unspecified key dimension time has become a slider widget.

The image can be saved as a html file by uncomenting the cell below:

# hv.save(im_hist, 'hist.html')

See also

To see the above example in full interactivity, you might want to use the following html link

Box stream

We will use the BoxDraw stream to draw region of interests (e.g. ROIs) over a set of surface salinity data, and use them to compute and display timeseries of the activity in the regions of interests.

Before going further we will work the Xarray dataset a bit…

First, we will change the latitude and longitude to integer as the Box stream does not seem to accept floating values (or at least I didn;t find the trick yet).

slice_ds.coords['y'] = ('latitude',slice_ds.latitude)
slice_ds.coords['x'] = ('longitude',slice_ds.longitude)
slice_ds2 = slice_ds.swap_dims({'latitude':'y'})
slice_ds2 = slice_ds2.swap_dims({'longitude':'x'})
slice_ds2 = slice_ds2.drop(['latitude'])
slice_ds2 = slice_ds2.drop(['longitude'])
slice_ds2 =  slice_ds2.assign_coords( x=(np.arange(slice_ds.x.shape[0])),
                                 y=(np.arange(slice_ds.y.shape[0])),
                                 ) 
slice_ds2
<xarray.Dataset>
Dimensions:   (k: 5, time: 61, x: 50, y: 100)
Coordinates:
  * time      (time) datetime64[ns] 2018-02-28T14:00:00 ... 2018-04-29T14:00:00
  * k         (k) float64 -8.8 -5.55 -3.0 -1.5 -0.5
  * y         (y) int64 0 1 2 3 4 5 6 7 8 9 10 ... 90 91 92 93 94 95 96 97 98 99
  * x         (x) int64 0 1 2 3 4 5 6 7 8 9 10 ... 40 41 42 43 44 45 46 47 48 49
Data variables:
    mean_cur  (time, k, y, x) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
    salt      (time, k, y, x) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
    temp      (time, k, y, x) float32 dask.array<chunksize=(31, 5, 100, 50), meta=np.ndarray>
Attributes: (12/21)
    Conventions:                     CF-1.0
    NCO:                             4.4.4
    Run_ID:                          2
    _CoordSysBuilder:                ucar.nc2.dataset.conv.CF1Convention
    aims_ncaggregate_buildDate:      2020-08-21T14:27:56+10:00
    aims_ncaggregate_datasetId:      products__ncaggregate__ereefs__gbr4_v2__...
    ...                              ...
    paramhead:                       GBR 4km resolution grid
    shoc_version:                    v1.1 rev(5620)
    technical_guide_link:            https://eatlas.org.au/pydio/public/aims-...
    technical_guide_publish_date:    2020-08-18
    title:                           eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d...
    DODS_EXTRA.Unlimited_Dimension:  time
saltXY = slice_ds2.mean(dim='k').salt.load()

The following is from the HoloViews example and define the ROIs functionality:

hv_ds = hv.Dataset(saltXY)

# Create stack of images grouped by time
im = hv_ds.to(hv.Image, ['x','y'], dynamic=True).opts(active_tools=['wheel_zoom', 'pan'], cmap=cmocean.cm.curl,
                     colorbar=True, width=450, height=400, clim=(34,36))

polys = hv.Polygons([])

box_stream = hv.streams.BoxEdit(source=polys)

# Declare an empty DataFrame to declare the types
empty = pd.DataFrame({'time': np.array([], dtype='datetime64[ns]'), 'salt': []})

def roi_curves(data):
    if not data or not any(len(d) for d in data.values()):
        return hv.NdOverlay({0: hv.Curve(empty, 'time', 'salt')})

    curves = {}
    data = zip(data['x0'], data['x1'], data['y0'], data['y1'])
    for i, (x0, x1, y0, y1) in enumerate(data):
        selection = hv_ds.select(x=(x0, x1), y=(y0, y1))
        curves[i] = hv.Curve(selection.aggregate('time', np.mean))
    return hv.NdOverlay(curves)

# Generate VLines by getting time value from the image frames
def vline(frame):
    return hv.VLine(frame.data.time.values)
vlines = im.apply(vline)

dmap = hv.DynamicMap(roi_curves, streams=[box_stream])

Important

To define an ROI, select the Box edit tool and double click to start defining the ROI and double click to finish placing the ROI:

(im * polys + dmap * vlines ).opts(title=titleName)